#pragma once

#include <algorithm/shared/utils/Vec3.h>
#include <utils/Chrono.h>
#include <utils/Math.h>

#include <numbers>
#include <tuple>

namespace solar
{
    class Geometry;

    constexpr auto degreeToRadian(const double& degree) -> double
    {
        return degree / 180 * std::numbers::pi_v<double>;
    }

    auto angleToDirection(double azimuth, double elevation) -> Vec3;

    auto radianToDirection(double azimuth_radian, double elevation_radian) -> Vec3;

    /// \brief 将向量投影到指定平面上
    /// \param vec 要投影的向量
    /// \param normal 目标平面的法向
    auto projectTo(const Vec3& vec, const Vec3& normal) -> Vec3;

    /// \brief 射线与平面求交
    auto intersect(const Vec3& normal, const Vec3& plane_origin, const Vec3& vertex,
                   const Vec3& direction) -> std::tuple<Vec3, double>;

    auto getDistanceFromHeliostatToReceiver(Geometry* geometry_heliostat,
                                            Geometry* geometry_receiver) -> double;

    auto getFocusCenter(Geometry* geometry_heliostat, Geometry* geometry_receiver) -> Vec3;

    auto getCenter(Geometry* input) -> Vec3;

    auto getNormal(Geometry* input) -> Vec3;

    auto getArea(Geometry* input) -> double;

    constexpr auto getSolarInclinationAngle(int year, unsigned int month,
                                            unsigned int day) -> double
    {
        const auto day_of_the_year = getDayOfTheYear(year, month, day);
        double f0 = 360 * day_of_the_year / 365.0;
        double f1 = 0.006918;
        double f2 = 0.399912 * const_expr::cos(degreeToRadian(f0));
        double f3 = 0.070257 * const_expr::sin(degreeToRadian(f0));
        double f4 = 0.006758 * const_expr::cos(degreeToRadian(f0 * 2));
        double f5 = 0.000907 * const_expr::sin(degreeToRadian(f0 * 2));
        double f6 = 0.002697 * const_expr::cos(degreeToRadian(f0 * 3));
        double f7 = 0.001480 * const_expr::sin(degreeToRadian(f0 * 3));
        double f = (f1 - f2 + f3 - f4 + f5 - f6 + f7) * 180.0 / std::numbers::pi_v<double>;
        return degreeToRadian(f);
    }

    /// @returns (azimuth_angle, elevation_angle) in radian
    constexpr auto getSolarIncidentAngles(int year, unsigned int month, unsigned int day,
                                          double hour, double longitude,
                                          double latitude) -> std::pair<double, double>
    {
        auto inclination_angle = getSolarInclinationAngle(year, month, day);
        double p3 = degreeToRadian(15 * hour + longitude - 300);
        double lat = degreeToRadian(latitude);
        double elevation_angle = const_expr::asin(
            const_expr::sin(lat) * const_expr::sin(inclination_angle) +
            const_expr::cos(lat) * const_expr::cos(inclination_angle) * const_expr::cos(p3));
        double azimuth_angle = const_expr::acos(
            (const_expr::sin(elevation_angle) * const_expr::sin(lat) - inclination_angle) /
            (const_expr::cos(inclination_angle) * const_expr::sin(lat)));
        return std::make_pair(azimuth_angle, elevation_angle);
    }
} // namespace solar
